feat: multi-buyer identity support in AI Credits widget#136
Conversation
Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR expands the AI Credits widget runtime/session model from a single deterministic buyer identity to a multi-buyer identity system per payer wallet, adding UI affordances to create/import/watch buyers, switching active buyer context, and filtering history by buyer.
Changes:
- Introduces
BuyerRecord[]+activeBuyerAddressinto runtime state/session (with legacy session migration) and adds new adapter actions for buyer lifecycle (create/select/import/watch/deep-link). - Updates Manage + History UI to support buyer selection and buyer-based history filtering.
- Adds Storybook QA fixtures and Playwright smoke coverage for multi-buyer flows.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/widgets/ai-credits-widget/states.spec.ts | Adds Playwright smoke tests + screenshots for multi-buyer UI states. |
| packages/ai-credits-widget/src/widgetRuntimeContract.ts | Extends adapter state/actions for multi-buyer support and re-exports BuyerRecord. |
| packages/ai-credits-widget/src/useAiCreditsHistory.ts | Adds buyer filter state/action and client-side filtering by entry.buyerAddress. |
| packages/ai-credits-widget/src/payerSession.ts | Replaces legacy single-buyer session fields with buyers[] + activeBuyerAddress and migration logic. |
| packages/ai-credits-widget/src/index.ts | Exports the new buyer-related types for consumers. |
| packages/ai-credits-widget/src/components/manage/BuyerOperatorCard.tsx | Adds buyer selector UI and import/watch panel; disables signing for address-only buyers. |
| packages/ai-credits-widget/src/components/history/HistoryTab.tsx | Adds buyer filter dropdown fed by known buyers. |
| packages/ai-credits-widget/src/buyerKeyDerivation.ts | Makes derivation message index-aware while preserving index 0 legacy message. |
| packages/ai-credits-widget/src/AiCreditsWidget.tsx | Wires knownBuyers to History UI and defaults history filter to active buyer. |
| packages/ai-credits-widget/src/adapter.ts | Implements multi-buyer session wiring + new actions (create/select/import/watch/deep-link) and URL param parsing. |
| examples/storybook/src/stories/helpers/aiCreditsWidgetStories.tsx | Adds multi-buyer QA fixture stories. |
| examples/storybook/src/stories/ai-credits-widget/AiCreditsWidgetQA.stories.tsx | Exposes new QA fixtures in Storybook. |
Suppressed comments (3)
packages/ai-credits-widget/src/adapter.ts:894
- Deep-link signature validation currently accepts any non-empty hex string (
/^0x[0-9a-fA-F]{2,}$/), so malformed/too-short signatures will be treated as “valid”. At minimum, require a 64- or 65-byte signature (EIP-2098 compact or standard) before accepting URL params.
if (!/^0x[0-9a-fA-F]{2,}$/.test(trimmedSignature)) {
setState((prev) =>
withDerivedStatus(prev, { error: 'Deep-link buyer signature is invalid' }, true),
)
return
packages/ai-credits-widget/src/adapter.ts:1457
- Deep-link parsing runs before a payer wallet is connected. When
buyerAddress/buyerSignatureare present butaddressis still null,handleApplyDeepLinkBuyersets an error (“Connect your wallet…”) on mount. Since the effect re-runs after connect anyway, defer applying deep-link params untiladdressis available to avoid a spurious error flash.
useEffect(() => {
if (typeof window === 'undefined') return
const params = new URLSearchParams(window.location.search)
const urlBuyerAddress = params.get('buyerAddress')
const urlBuyerSignature = params.get('buyerSignature')
packages/ai-credits-widget/src/components/history/HistoryTab.tsx:524
- Options in
BuyerFilterSelecthaverole="option"but don’t indicate selection viaaria-selected, which is part of the listbox pattern. This makes it harder for screen readers to announce which buyer filter is currently selected.
tag="button"
role="option"
paddingHorizontal="$3"
paddingVertical="$2"
cursor="pointer"
…ts widget - Added support for deep link parameters, including buyer address and operator signature, to facilitate buyer registration via NCDI deep links. - Introduced validation functions for buyer address and operator signature. - Enhanced state management to handle operator consent and session updates based on deep link data. - Updated relevant components and types to accommodate new deep link functionality, ensuring a seamless user experience when applying deep links.
- Introduced new functions for managing deep link parameters, including storage and retrieval from local storage. - Updated error handling to provide clearer messages when deep link parameters are invalid or missing. - Improved state management to reflect deep link status and errors in the UI, ensuring a better user experience. - Added a fallback mechanism for deep link processing, enhancing robustness in various scenarios.
…ng and retrieval - Added a buyers array to track buyer addresses and consent timestamps in the state. - Implemented getBuyerAddresses method to retrieve buyer addresses for a given payer. - Updated submitOperatorConsent to include payer information and manage buyer consent state effectively. - Refactored state management to ensure consistency and clarity in buyer handling.
|
@sirpy |
…e buyer address management - Implemented discoverBuyers action to track and manage buyer addresses dynamically. - Enhanced the adapter to resolve local buyer addresses and select preferred buyers based on history and session data. - Updated the widget to trigger buyer discovery during history retrieval, improving user experience. - Refactored state management to include known buyers and streamline buyer address handling across components.
|
@blueogin is mock delay set to 0 in non storybook env? it should. it shouldnt be in production |
sirpy
left a comment
There was a problem hiding this comment.
i dont understand the latest changes.
buyers is very simple
user add buyer via private key, or generate one based on signature or add it via url params.
when user add buyer it is saved on localstorage.
this is the list of user buyers.
that's it. simple.
…and stories - Introduced derivedBuyerAddress to the AiCreditsWidgetAdapterState for improved buyer address handling. - Updated relevant components and stories to utilize derivedBuyerAddress, enhancing the management of buyer selections. - Added utility function to determine if the selected buyer is derived, improving the logic in purchase flow handling.
- Introduced `operatorConsentPending` to the `AiCreditsWidgetAdapterState` for tracking the consent submission process. - Updated relevant components and hooks to handle the new state, improving user feedback during the operator consent flow. - Enhanced UI elements to reflect the pending state, ensuring a smoother user experience during consent submission. - Adjusted Storybook stories to demonstrate the new consent pending behavior.
A payer wallet was limited to a single deterministic buyer identity. This adds full multi-buyer identity management: create additional derived buyers, import via private key, register address-only (view/consent) buyers, switch between them, and deep-link assign a partner buyer via URL params.
Runtime contract & session model
BuyerRecordtype withtype: 'derived' | 'imported' | 'address-only',derivationIndex, andlabelpayerSessionnow storesbuyers: BuyerRecord[]+activeBuyerAddress; old single-buyer sessions auto-migrate on first readbuyersandactiveBuyerAddress;buyerPubKey/buyerPrvKeyremain as computed fields from the active buyerDerivation
buildBuyerKeyMessage(address, index)— index0preserves the exact legacy message for backward compatibility; index> 0appends(buyer N)New adapter actions
createBuyerselectBuyer(address)importBuyerFromPrivateKey(key)0x…64hex, derives address, registers asimportedselectBuyerByAddress(address)applyDeepLinkBuyer(address, sig)?buyerAddress=&buyerSignature=URL params on mount, registers partner buyerUI
BuyerOperatorCard: buyer selector list (visible when >1 buyer), "New Buyer" button, collapsible "Import Key" / "Watch Address" panel; address-only buyers show a "view only" badge and Sign Consent is disabledHistoryTab:BuyerFilterSelectdropdown fed via newknownBuyersprop; defaults to active buyer on history tab openuseAiCreditsHistory:buyerAddressFilterstate +setBuyerAddressFilteraction; client-side filtering onentry.buyerAddressStories & tests
Three new QA fixtures (
MultiBuyerManage,AddressOnlyBuyer,MultiBuyerHistory) and four Playwright smoke tests covering the new flows.